//! Concurrency statement parsing (select, go)

use super::super::{parse_expression, Parser};
use crate::ast::expr::Expression;
use crate::ast::stmt::{GoStmt, SelectCaseStmt, SelectStmt};
use crate::error::{CompilerError, Result};
use crate::lexer::token::Token;

/// Parse select statement
pub fn parse_select_statement(parser: &mut Parser) -> Result<SelectStmt> {
    parser.expect(&Token::Select)?;
    parser.expect(&Token::LeftBrace)?;

    let mut cases = Vec::new();
    let mut default_case = None;

    while !parser.check(&Token::RightBrace) {
        if parser.consume(&Token::Default)? {
            parser.expect(&Token::Arrow)?;
            default_case = super::parse_statement(parser)?;
            break;
        } else {
            let channel = parse_expression(parser)?;
            if channel.is_none() {
                break;
            }

            parser.expect(&Token::Arrow)?;

            let body = super::parse_statement(parser)?;
            if let Some(body) = body {
                cases.push(SelectCaseStmt {
                    channel: channel.unwrap(),
                    body,
                    location: parser.current_location(),
                });
            }
        }

        if !parser.consume(&Token::Comma)? {
            break;
        }
    }

    parser.expect(&Token::RightBrace)?;

    Ok(SelectStmt {
        cases,
        default_case: default_case.map(Box::new),
        location: parser.current_location(),
    })
}

/// Parse go statement
pub fn parse_go_statement(parser: &mut Parser) -> Result<GoStmt> {
    parser.expect(&Token::Go)?;

    let call = parse_expression(parser)?;
    if let Some(Expression::Call(call_expr)) = call {
        parser.expect(&Token::Semicolon)?;

        Ok(GoStmt {
            call: call_expr,
            location: parser.current_location(),
        })
    } else {
        Err(CompilerError::syntax(
            parser.current_location().line,
            parser.current_location().column,
            "Expected function call after 'go'",
        ))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::parser::Parser;

    fn parse_go_stmt(input: &str) -> Result<GoStmt> {
        let mut parser = Parser::new(input.to_string(), None);
        parser.advance()?;
        parse_go_statement(&mut parser)
    }

    fn parse_select_stmt(input: &str) -> Result<SelectStmt> {
        let mut parser = Parser::new(input.to_string(), None);
        parser.advance()?;
        parse_select_statement(&mut parser)
    }

    #[test]
    fn test_parse_go_statement() {
        // go statement requires a function call expression
        let result = parse_go_stmt("go foo();");
        assert!(result.is_ok());
        let stmt = result.unwrap();
        assert!(matches!(
            *stmt.call.callee,
            crate::ast::expr::Expression::Variable(_)
        ));
    }

    #[test]
    fn test_parse_go_statement_with_args() {
        let result = parse_go_stmt("go process(data);");
        assert!(result.is_ok());
    }

    #[test]
    fn test_parse_go_statement_not_call_error() {
        let result = parse_go_stmt("go x;");
        assert!(result.is_err());
    }

    #[test]
    fn test_parse_select_statement() {
        // select uses comma-separated cases
        let result =
            parse_select_stmt("select { ch1 -> println(\"one\");, ch2 -> println(\"two\"); }");
        if result.is_err() {
            // Try without comma if that's the syntax
            let result2 =
                parse_select_stmt("select { ch1 -> println(\"one\"); ch2 -> println(\"two\"); }");
            assert!(result2.is_ok());
            let stmt = result2.unwrap();
            assert_eq!(stmt.cases.len(), 2);
        } else {
            let stmt = result.unwrap();
            assert_eq!(stmt.cases.len(), 2);
        }
    }

    #[test]
    fn test_parse_select_statement_with_default() {
        let result = parse_select_stmt(
            "select { ch -> println(\"message\");, default -> println(\"timeout\"); }",
        );
        if result.is_err() {
            // Try without comma
            let result2 = parse_select_stmt(
                "select { ch -> println(\"message\"); default -> println(\"timeout\"); }",
            );
            assert!(result2.is_ok());
            let stmt = result2.unwrap();
            assert!(stmt.default_case.is_some());
        } else {
            let stmt = result.unwrap();
            assert!(stmt.default_case.is_some());
        }
    }

    #[test]
    fn test_parse_select_statement_empty() {
        let result = parse_select_stmt("select { }");
        assert!(result.is_ok());
        let stmt = result.unwrap();
        assert_eq!(stmt.cases.len(), 0);
    }
}
